package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.constant.RoleType;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.RoleCreateDTO;
import com.cloudfun.campusshare.common.model.dto.RoleModifyDTO;
import com.cloudfun.campusshare.common.model.vo.RoleDetailVO;
import com.cloudfun.campusshare.common.model.vo.RoleListVO;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.common.security.UserAuthentication;
import com.cloudfun.campusshare.entity.RoleAuthorityRel;
import com.cloudfun.campusshare.entity.RoleEntity;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.entity.UserRoleEntity;
import com.cloudfun.campusshare.repo.*;
import com.cloudfun.campusshare.service.CreatorPersistenceService;
import com.cloudfun.campusshare.service.RoleService;
import com.cloudfun.campusshare.service.UserRelService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2019/12/23
 */
@Service
public class RoleServiceImpl extends CreatorPersistenceService implements RoleService {

    @Autowired
    private RoleEntityRepo roleEntityRepo;

    @Autowired
    private UserRoleEntityRepo userRoleEntityRepo;

    @Autowired
    private RoleAuthorityRelRepo roleAuthorityRelRepo;

    @Autowired
    private RoleRelRepo roleRelRepo;

    @Autowired
    private UserRelService userRelService;

    private static final String HEAD_PAGE_AUTHORITY = "109";

    @Override
    protected Class<?> getEntityClass() {
        return RoleEntity.class;
    }

    @Override
    @Transactional
    public void create(RoleCreateDTO createDTO) {
        UserEntity userMerchant = userRelService.getUserMerchant(AuthenticationHolder.getUserId());

        RoleEntity roleEntity = roleEntityRepo.findByNameAndAndMerchantId(createDTO.getName(),userMerchant.getId());

        if (null != roleEntity) {
            throw new BusinessException(Code.DATA_EXIST, "角色已存在");
        }
        List<String> authorityIds = createDTO.getAuthorityIds();
        if(!(AuthenticationHolder.isMerchant() || AuthenticationHolder.isAdmin()) && authorityIds.contains(HEAD_PAGE_AUTHORITY)){
            throw new BusinessException(Code.SHOW_HEADPAGE_AUTHORITY_MISSING, "非运营商，不允许展示首页");
        }
        roleEntity = new RoleEntity();
        BeanUtil.copyProperties(createDTO, roleEntity);
        roleEntity.setEnable(true);
        roleEntity.setMerchantId(userMerchant.getId());
        roleEntityRepo.save(roleEntity);
        String roleId = roleEntity.getId();
        // 保存权限
        this.saveAuthorities(roleId, createDTO.getAuthorityIds());
        // 保存可分配角色
        this.saveRoleRels(roleId, createDTO.getSlaveRoleIds());
    }

    @Override
    @Transactional
    public void modify(RoleModifyDTO modifyDTO) {
        RoleEntity roleEntity = roleEntityRepo.findById(modifyDTO.getId()).orElseThrow(() -> new BusinessException(Code.NODATA, "角色不存在"));
        if (StringUtils.isNotBlank(modifyDTO.getName()) && !modifyDTO.getName().equals(roleEntity.getName())) {
            RoleEntity roleEntityByName = roleEntityRepo.findByNameAndAndMerchantId(modifyDTO.getName(),roleEntity.getMerchantId());
            if (null != roleEntityByName) {
                throw new BusinessException(Code.DATA_EXIST, "角色已存在");
            }
        }
        List<String> authorityIds = modifyDTO.getAuthorityIds();
        if(!(AuthenticationHolder.isMerchant() || AuthenticationHolder.isAdmin()) && authorityIds.contains(HEAD_PAGE_AUTHORITY)){
            throw new BusinessException(Code.SHOW_HEADPAGE_AUTHORITY_MISSING, "非运营商，不允许展示首页");
        }
        BeanUtil.copyProperties(modifyDTO, roleEntity, CopyOptions.create().setIgnoreNullValue(true));
        roleEntityRepo.save(roleEntity);
        String roleId = roleEntity.getId();
        // 保存权限
        this.saveAuthorities(roleId, modifyDTO.getAuthorityIds());
        // 保存可分配角色
        this.saveRoleRels(roleId, modifyDTO.getSlaveRoleIds());
    }

    /**
     * 保存权限
     *
     * @param roleId
     * @param authorityIds
     */
    private void saveAuthorities(String roleId, List<String> authorityIds) {
        List<RoleAuthorityRel> toBeDeleteList = new ArrayList<>();
        List<String> authorityIdsInDB = roleAuthorityRelRepo.findAllByRoleId(roleId).stream()
                .peek(entity -> {
                    if (!authorityIds.contains(entity.getAuthorityId())) {
                        toBeDeleteList.add(entity);
                    }
                })
                .map(RoleAuthorityRel::getAuthorityId).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(toBeDeleteList)) {
            roleAuthorityRelRepo.deleteAll(toBeDeleteList);
        }

        authorityIds.removeAll(authorityIdsInDB);
        List<RoleAuthorityRel> roleAuthorityRelList = authorityIds.stream()
                .map(authId -> {
                    RoleAuthorityRel roleAuthorityRel = new RoleAuthorityRel();
                    roleAuthorityRel.setRoleId(roleId);
                    roleAuthorityRel.setAuthorityId(authId);
                    return roleAuthorityRel;
                }).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(roleAuthorityRelList)) {
            roleAuthorityRelRepo.saveAll(roleAuthorityRelList);
        }
    }

    /**
     * 保存可分配角色
     *
     * @param roleId
     * @param slaveRoleIds
     */
    private void saveRoleRels(String roleId, List<String> slaveRoleIds) {
        List<RoleRel> toBeDeleteList = new ArrayList<>();
        List<String> slaveRoleIdInDB = roleRelRepo.findAllByMasterId(roleId).stream()
                .peek(entity -> {
                    if (!slaveRoleIds.contains(entity.getSlaveId())) {
                        toBeDeleteList.add(entity);
                    }
                })
                .map(RoleRel::getSlaveId).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(slaveRoleIdInDB)) {
            roleRelRepo.deleteAll(toBeDeleteList);
        }

        slaveRoleIds.removeAll(slaveRoleIdInDB);
        List<RoleRel> roleRelList = slaveRoleIds.stream()
                .map(slaveId -> {
                    RoleRel roleRel = new RoleRel();
                    roleRel.setMasterId(roleId);
                    roleRel.setSlaveId(slaveId);
                    return roleRel;
                }).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(roleRelList)) {
            roleRelRepo.saveAll(roleRelList);
        }
    }

    @Transactional
    @Override
    public void bindRoleTypes(String userId, List<RoleType> roleTypes) {
        if(!CollectionUtils.isEmpty(roleTypes)){
            Set<String> roles = roleTypes.stream().map(RoleType::toString).collect(Collectors.toSet());
            List<RoleEntity> roleEntities = roleEntityRepo.findAllByTypeIn(roles);
            List<String> roleIds = roleEntities.stream().map(RoleEntity::getId).collect(Collectors.toList());
            this.bindRoles(userId, roleIds);
        }
    }

    @Override
    @Transactional
    public void bindRoles(String userId, List<String> roleIds) {
        List<UserRoleEntity> userRoleEntities = userRoleEntityRepo.findAllByUserId(userId);
        List<String> roleIdInDB = userRoleEntities.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toList());

        roleIds.removeAll(roleIdInDB);
        if (CollectionUtils.isEmpty(roleIds)) {
            return;
        }
        // 绑定需要绑定的角色
        List<RoleEntity> roleEntities = roleEntityRepo.findAllByIdIn(roleIds);
        List<UserRoleEntity> userRoles = roleEntities.stream()
                .map(role -> {
                    UserRoleEntity userRoleEntity = new UserRoleEntity();
                    userRoleEntity.setUserId(userId);
                    userRoleEntity.setRoleId(role.getId());
                    return userRoleEntity;
                }).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(userRoles)) {
            userRoleEntityRepo.saveAll(userRoles);
        }
    }

    @Override
    public void unbindRoles(String userId) {
        List<UserRoleEntity> userRoleInDB = userRoleEntityRepo.findAllByUserId(userId);
        userRoleEntityRepo.deleteAll(userRoleInDB);
    }

    @Override
    public Set<String> getRoleTypes(String userId) {
        if (StringUtils.isBlank(userId)) {
            return new HashSet<>();
        }
        List<UserRoleEntity> userRoles = userRoleEntityRepo.findAllByUserId(userId);
        Set<String> roleIds = userRoles.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toSet());
        List<RoleEntity> roles = roleEntityRepo.findAllByIdInAndEnable(roleIds, true);

        return roles.stream().map(RoleEntity::getType).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
    }

    @Override
    public Set<String> getRoleIds(String userId) {
        List<UserRoleEntity> userRoles = userRoleEntityRepo.findAllByUserId(userId);
        Set<String> roleIds = userRoles.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toSet());
        List<RoleEntity> roles = roleEntityRepo.findAllByIdInAndEnable(roleIds, true);
        return roles.stream().map(RoleEntity::getId).collect(Collectors.toSet());
    }

    @Override
    public void fillUserAuthentication(UserAuthentication userAuthentication) {
        List<UserRoleEntity> userRoles = userRoleEntityRepo.findAllByUserId(userAuthentication.getUserId());
        Set<String> roleIds = userRoles.stream().map(UserRoleEntity::getRoleId).collect(Collectors.toSet());
        List<RoleEntity> roles = roleEntityRepo.findAllByIdInAndEnable(roleIds, true);
        Set<String> roleTypes = roles.stream().map(RoleEntity::getType).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        Optional<RoleEntity> viewAllDataOpt = roles.stream().filter(RoleEntity::getViewAllData).findAny();
        userAuthentication.setRoleTypes(roleTypes);
        userAuthentication.setRoleIds(roleIds);
        userAuthentication.setViewAllData(viewAllDataOpt.isPresent());
    }

    @Override
    public PageResult<RoleListVO> search(Pageable pageable, String keyword) {
        Page<RoleEntity> result = roleEntityRepo.findAll(this.spec(keyword, null), pageable);
        List<RoleListVO> billListVOList = result.getContent().stream()
                .map(this::toRoleListVO).collect(Collectors.toList());
        return new PageResult<>(result.getTotalElements(), billListVOList);
    }

    private Specification<RoleEntity> spec(String keyword, Boolean enable) {
        return (root, query, criteriaBuilder) -> {
            Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
            String userId = AuthenticationHolder.getUserId();
            List<Predicate> list = new ArrayList<>();
            if (!AuthenticationHolder.isAdmin()) {
                if (viewAllData) {
                    UserEntity userMerchant = userRelService.getUserMerchant(userId);
                    list.add(criteriaBuilder.equal(root.get("merchantId"), userMerchant.getId()));
                } else {
                    Set<String> descendantIds = userRelService.getDescendantIds(userId);
                    descendantIds.add(userId);
                    list.add(root.get("creatorId").in(descendantIds));
                }
            }
            if (StringUtils.isNotBlank(keyword)) {
                list.add(criteriaBuilder.like(root.get("name"), "%" + keyword + "%"));
            }
            if (null != enable) {
                list.add(criteriaBuilder.equal(root.get("enable"), enable));
            }
            Predicate[] predicates = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(predicates));
        };
    }

    private RoleListVO toRoleListVO(RoleEntity roleEntity) {
        RoleListVO roleListVO = new RoleListVO();
        BeanUtil.copyProperties(roleEntity, roleListVO, "creator");
        return roleListVO;
    }

    @Override
    @Transactional
    public void delete(String id) {
        List<UserRoleEntity> userRoleEntities = userRoleEntityRepo.findAllByRoleId(id);
        if (!CollectionUtils.isEmpty(userRoleEntities)) {
            userRoleEntityRepo.deleteAll(userRoleEntities);
        }
        List<RoleAuthorityRel> roleAuthorityRelList = roleAuthorityRelRepo.findAllByRoleId(id);
        if (!CollectionUtils.isEmpty(roleAuthorityRelList)) {
            roleAuthorityRelRepo.deleteAll(roleAuthorityRelList);
        }
        List<RoleRel> roleRelList = roleRelRepo.findAllBySlaveId(id);
        if (!CollectionUtils.isEmpty(roleRelList)) {
            roleRelRepo.deleteAll(roleRelList);
        }
        List<RoleRel> roleRelAllByMasterId = roleRelRepo.findAllByMasterId(id);
        if (!CollectionUtils.isEmpty(roleRelAllByMasterId)) {
            roleRelRepo.deleteAll(roleRelAllByMasterId);
        }

        roleEntityRepo.deleteById(id);
    }

    @Override
    public RoleDetailVO detail(String id) {
        RoleEntity roleEntity = roleEntityRepo.findById(id).orElseThrow(() -> new BusinessException(Code.NODATA));
        RoleDetailVO roleDetailVO = new RoleDetailVO();
        BeanUtil.copyProperties(roleEntity, roleDetailVO);
        List<String> authorityIdsInDB = roleAuthorityRelRepo.findAllByRoleId(id).stream()
                .map(RoleAuthorityRel::getAuthorityId).collect(Collectors.toList());
        roleDetailVO.setAuthorityIds(authorityIdsInDB);
        List<String> slaveRoleIdInDB = roleRelRepo.findAllByMasterId(id).stream().map(RoleRel::getSlaveId).collect(Collectors.toList());
        roleDetailVO.setSlaveRoleIds(slaveRoleIdInDB);
        return roleDetailVO;
    }

    @Override
    public void enableOrDisable(String id, Boolean enable) {
        RoleEntity roleEntity = roleEntityRepo.findById(id).orElseThrow(() -> new BusinessException(Code.NODATA));
        roleEntity.setEnable(enable);
        roleEntityRepo.save(roleEntity);
    }

    @Override
    public List<RoleListVO> getSimpleList() {
        List<RoleEntity> roleEntities;
        if (AuthenticationHolder.isAdmin()) {
            roleEntities = roleEntityRepo.findAllByEnable(true);
        } else if (AuthenticationHolder.isMerchant()) {
            roleEntities = new ArrayList<>(roleEntityRepo.findAll(this.spec(null, true)));
        } else {
            List<String> slaveRoleIds = roleRelRepo.findAllByMasterIdIn(AuthenticationHolder.getUserAuth().getRoleIds())
                    .stream().map(RoleRel::getSlaveId).collect(Collectors.toList());
            roleEntities = roleEntityRepo.findAllByIdInAndEnable(slaveRoleIds, true);
        }
        return roleEntities.stream()
                .filter(entity -> !RoleType.CUSTOMER.equals(entity.getType()))
                .map(this::toRoleListVO).collect(Collectors.toList());
    }

    @Override
    public Optional<RoleEntity> getById(String id) {
        return roleEntityRepo.findById(id);
    }
}
